#include <iostream>
#include <iomanip>
#include <random>
#include <vector>
#include <chrono>
#include <cassert>
#include <ratio>
#include <cmath>
#include <cstring>
#include <fstream>
#include <filesystem>
#include "mtree/mtree.hpp"

#define KEYLEN 16

using namespace std;
using namespace MetricSpaceBenchmark::MetricIndex::MTree;

static long long m_id = 1;       // general id's
static long long g_id = 1000000; // cluster id's

const int NR = 4;
const int LC = 50;

static random_device m_rd;
static mt19937_64 m_gen(m_rd());
static uniform_real_distribution<double> m_distrib(-1.0, 1.0);

namespace fs = std::filesystem;

void create_directory_if_not_exists(const string &path) {
    if (!fs::exists(path)) {
        fs::create_directories(path);  // 自动创建目录
    }
}

struct KeyObject {
    double key[KEYLEN]{};

    KeyObject() { for (double &i: this->key) i = 0; };

    explicit KeyObject(const double key[]) {
        memcpy(this->key, key, KEYLEN * sizeof(double));
    }

    KeyObject(const KeyObject &other) {
        memcpy(this->key, other.key, KEYLEN * sizeof(double));
    }

    KeyObject &operator=(const KeyObject &other) {
        memcpy(this->key, other.key, KEYLEN * sizeof(double));
        return *this;
    }

    [[nodiscard]] double distance(const KeyObject &other) const {
        double sum = 0;
        for (int i = 0; i < KEYLEN; i++) {
            sum += pow(key[i] - other.key[i], 2.0);
        }
        return sqrt(sum);
    }
};

struct performanceMetric {
    double avg_build_ops;
    double avg_build_time;
    double avg_query_ops;
    double avg_query_time;
    size_t avg_memory_used;
};


int generate_center(double center[]) {
    for (int i = 0; i < KEYLEN; i++) {
        center[i] = m_distrib(m_gen);
    }
    return 1;
}

unsigned long generate_data(vector<Entry<KeyObject>> &entries, const int n) {

    double buf[KEYLEN];
    for (int i = 0; i < n; i++) {
        generate_center(buf);
        Entry<KeyObject> entry(m_id++, KeyObject(buf));
        entries.push_back(entry);
    }
    return entries.size();
}

int generate_cluster(vector<Entry<KeyObject>> &entries, double center[], const int n, const double radius) {
    double diff = sqrt(pow(radius, 2.0) / KEYLEN);
    uniform_real_distribution<double> m_eps(-diff, diff);

    double val[KEYLEN];
    entries.emplace_back(g_id++, KeyObject(center));
    for (int i = 0; i < n - 1; i++) {
        memcpy(val, center, KEYLEN * sizeof(double));
        if (radius > 0) {
            for (int j = 0; j < KEYLEN; j++) {
                val[j] = center[j] + m_eps(m_gen);
            }
        }
        entries.emplace_back(g_id++, KeyObject(val));
    }
    return n;
}

void do_run(const int index, const int n_entries, const int n_clusters, const int clusterSize, const double radius,
            vector<struct performanceMetric> &metrics) {
    m_id = 1;
    g_id = 100000000;

    MTree<KeyObject, NR, LC> mtree;

    size_t sz;
    chrono::duration<double, nano> total(0);
    RoutingObject<KeyObject>::n_build_ops = 0;

    vector<Entry<KeyObject>> entries;
    generate_data(entries, n_entries);
    assert((int) entries.size() == n_entries);

    auto s = chrono::steady_clock::now();
    for (auto &e: entries) {
        mtree.Insert(e);
    }
    auto e = chrono::steady_clock::now();
    total += (e - s);
    sz = mtree.size();

    struct performanceMetric m{};

    m.avg_build_ops = 100.0 * ((double) RoutingObject<KeyObject>::n_build_ops / (double) sz);
    m.avg_build_time = total.count() / (double) sz;

    cout << "(" << index << ") build tree: " << setw(10) << setprecision(6) << m.avg_build_ops << "% opers "
         << setw(10) << setprecision(6) << m.avg_build_time << " nanosecs ";

    double centers[n_clusters][KEYLEN];
    for (int i = 0; i < n_clusters; i++) {
        generate_center(centers[i]);
        vector<Entry<KeyObject>> cluster;
        generate_cluster(cluster, centers[i], clusterSize, radius);
        assert(cluster.size() == clusterSize);
        for (auto &e: cluster) {
            mtree.Insert(e);
        }

        sz = mtree.size();
        assert(sz == n_entries + clusterSize * (i + 1));
    }

    DBEntry<KeyObject>::n_query_ops = 0;
    chrono::duration<double, milli> querytime(0);
    for (int i = 0; i < n_clusters; i++) {
        auto s = chrono::steady_clock::now();
        vector<Entry<KeyObject>> results = mtree.RangeQuery(KeyObject(centers[i]), radius);
        auto e = chrono::steady_clock::now();
        querytime += (e - s);
    }

    m.avg_query_ops = 100.0 * ((double) DBEntry<KeyObject>::n_query_ops / (double) n_clusters / (double) sz);
    m.avg_query_time = (double) querytime.count() / (double) n_clusters;

    cout << " query ops " << dec << setprecision(6) << m.avg_query_ops << "% opers   "
         << "query time: " << dec << setprecision(6) << m.avg_query_time << " millisecs" << endl;


    m.avg_memory_used = mtree.memory_usage();

    metrics.push_back(m);

    mtree.Clear();
    assert(mtree.size() == 0);
}


void do_experiment(const int n, const int n_runs, const int n_entries, const int n_clusters, const int clusterSize,
                   const double radius) {

    // 定义输出文件路径
    const string file_path = "../../results/mtree/mtree_experiment_output.txt";

    // 确保目录存在
    create_directory_if_not_exists("../../results/mtree");

    // 打开文件并重新写入
    ofstream outFile(file_path, ios::out | ios::app
    ); // 只用 ios::out 会每次打开时清空文件

    if (!outFile.is_open()) {
        cerr << "Error opening file!" << endl;
        return;
    }

    outFile << "------------------ " << n << " -------------------" << endl;
    outFile << "dataset size, N = " << n_entries << endl;
    outFile << "no. clusters: " << n_clusters << endl;
    outFile << "cluster size " << clusterSize << endl;
    outFile << "radius: " << radius << endl;
    outFile << "averaged across " << n_runs << " runs" << endl;

    vector<performanceMetric> metrics;
    for (int i = 0; i < n_runs; i++) {
        do_run(i + 1, n_entries, n_clusters, clusterSize, radius, metrics);
    }

    double avg_build_ops = 0;
    double avg_build_time = 0;
    double avg_query_ops = 0;
    double avg_query_time = 0;
    double avg_memory = 0;
    for (struct performanceMetric &m: metrics) {
        avg_build_ops += m.avg_build_ops / n_runs;
        avg_build_time += m.avg_build_time / n_runs;
        avg_query_ops += m.avg_query_ops / n_runs;
        avg_query_time += m.avg_query_time / n_runs;
        avg_memory += (double) m.avg_memory_used / (double) n_runs;
    }

    outFile << "no. runs: " << metrics.size() << endl;
    outFile << "avg build:  " << avg_build_ops << "% opers " << avg_build_time << " nanosecs" << endl;
    outFile << "avg query:  " << avg_query_ops << "% opers " << avg_query_time << " millisecs" << endl;
    outFile << "Memory Usage: " << fixed << setprecision(2) << avg_memory / 1000000.0 << "MB" << endl;
    outFile << "-------------------------------------------------" << endl << endl;

    outFile.close();
}

int main(int argc, char **argv) {


    cout << "MTree data structure with parameters: " << endl;
    cout << "    no. routes in internal nodes: " << NR << endl;
    cout << "    leaf capacity: " << LC << endl << endl;


    const int N = 9;    // 实验中数据集的规模数，共有 9 种不同的规模
    const int n_entries[N] = {100000, 200000, 400000, 800000, 1000000, 2000000, 4000000, 8000000, 16000000};
    const int n_clusters = 10;//聚类数,设定为 10
    const int clusterSize = 10;// 每个聚类中的样本数,设定为 10
    const double radius = 0.04;//查询半径,设定为 0.04
    const int n_runs = 5;//每个配置运行的次数,设定为 5

    for (int i = 0; i < N; i++) {
        do_experiment(i + 1, n_runs, n_entries[i], n_clusters, clusterSize, radius);
    }

    cout << "Experiments for various radius queries" << endl;

    const int M = 10000;
    const int n_rad = 11;
    const double rad[n_rad] = {0, 0.02, 0.04, 0.06, 0.08, 0.10, 0.20, 0.40, 0.60, 0.80, 1.0};

    for (int i = 0; i < n_rad; i++) {
        do_experiment(i + 1, n_runs, M, n_clusters, clusterSize, rad[i]);
    }

    return 0;
}
